/* Copyright (c) [2025] [zhi1208@126.com]
 * SPDX-License-Identifier: MIT
 */

/*********************
 *      INCLUDES
 *********************/
#include "lvgl.h"
#include "hmi_pg_navigation.h"
#include "hmi_pg_def.h"
#include "hmi_pg_manager.h"

/*********************
 *      DEFINES
 *********************/
#define HMI_NAV_GESTURE_THRESH LV_DPX(40)

/**********************
 *      TYPEDEFS
 **********************/

/**********************
 *  STATIC PROTOTYPES
 **********************/
static void dect_zone_ev_cb(lv_event_t *e);

static hmi_pg_t **locate_pg_in_stack(hmi_pg_t *pg);
static void pg_exit_anim_completed_cb(lv_anim_t *a);
static void push_anim_completed_cb(lv_anim_t *a);
static void pop_anim_completed_cb(lv_anim_t *a);
static void back_to_anim_completed_cb(lv_anim_t *a);
static void pg_sw_anim(hmi_pg_t *enter_pg, hmi_pg_t *exit_pg, lv_anim_completed_cb_t cb);

/**********************
 *  STATIC VARIABLES
 **********************/
static lv_ll_t pg_stack;

static lv_obj_t *left_dect_zone;
static lv_obj_t *right_dect_zone;
static lv_obj_t *bottom_dect_zone;

/**********************
 *      MACROS
 **********************/

/**********************
 *   GLOBAL FUNCTIONS
 **********************/
void hmi_pg_navigation_init()
{
    lv_ll_init(&pg_stack, sizeof(hmi_pg_t *));

    left_dect_zone = lv_obj_create(lv_layer_sys());
    lv_obj_remove_style_all(left_dect_zone);
    lv_obj_set_size(left_dect_zone, lv_pct(5), lv_pct(90));
    lv_obj_align(left_dect_zone, LV_ALIGN_LEFT_MID, 0, 0);
    lv_obj_add_event_cb(left_dect_zone, dect_zone_ev_cb, LV_EVENT_ALL, NULL);

    right_dect_zone = lv_obj_create(lv_layer_sys());
    lv_obj_remove_style_all(right_dect_zone);
    lv_obj_set_size(right_dect_zone, lv_pct(5), lv_pct(90));
    lv_obj_align(right_dect_zone, LV_ALIGN_RIGHT_MID, 0, 0);
    lv_obj_add_event_cb(right_dect_zone, dect_zone_ev_cb, LV_EVENT_ALL, NULL);

    bottom_dect_zone = lv_obj_create(lv_layer_sys());
    lv_obj_remove_style_all(bottom_dect_zone);
    lv_obj_set_size(bottom_dect_zone, lv_pct(90), lv_pct(5));
    lv_obj_align(bottom_dect_zone, LV_ALIGN_BOTTOM_MID, 0, 0);
    lv_obj_add_event_cb(bottom_dect_zone, dect_zone_ev_cb, LV_EVENT_ALL, NULL);
}

void hmi_pg_push(hmi_pg_t *pg)
{
    LV_ASSERT_NULL(pg);

    hmi_pg_t **cur_pg = lv_ll_get_tail(&pg_stack);
    if (cur_pg && (*cur_pg == pg))
    {
        LV_LOG_WARN("The current page is already page [%s] and cannot be pushed onto the stack!", pg->name);
        return;
    }

    if (!hmi_pg_is_registered(pg))
    {
        if (!pg->create)
        {
            LV_LOG_WARN("The pointer to the creation function of the page [%s] is empty!", pg->name);
            return;
        }
        pg->create();
        hmi_register_pg(pg);
    }

    hmi_pg_t **pg_node = lv_ll_ins_tail(&pg_stack);
    if (!pg_node)
    {
        LV_LOG_WARN("Memory allocation failed. Page [%s] cannot be pushed in!", pg->name);
        return;
    }
    *pg_node = pg;

    ++(pg->statck_refs);

    if (cur_pg && *cur_pg)
    {
        LV_LOG_USER("[%s]-->[%s]", (*cur_pg)->name, pg->name);
        pg_sw_anim(pg, *cur_pg, push_anim_completed_cb);
    }
    else
    {
        lv_scr_load(pg->root);
        pg->enter_anim(push_anim_completed_cb, 0);
    }
}

void hmi_pg_pop()
{
    if (lv_ll_get_len(&pg_stack) <= 1)
    {
        LV_LOG_WARN("The stack is empty and cannot be popped!");
        return;
    }

    hmi_pg_t **cur_pg = lv_ll_get_tail(&pg_stack);
    hmi_pg_t **prev_pg = lv_ll_get_prev(&pg_stack, cur_pg);

    LV_LOG_USER("[%s]-->[%s]", (*cur_pg)->name, (*prev_pg)->name);
    pg_sw_anim(*prev_pg, *cur_pg, pop_anim_completed_cb);
}

void hmi_back_to(hmi_pg_t *pg)
{
    if (!locate_pg_in_stack(pg))
    {
        LV_LOG_WARN("The page is not in the stack!");
        return;
    }

    hmi_pg_t **cur_pg = lv_ll_get_tail(&pg_stack);

    if (*cur_pg == pg)
    {
        LV_LOG_WARN("It's already the page[%s]!", pg->name);
        return;
    }

    LV_LOG_USER("[%s]-->[%s]", (*cur_pg)->name, pg->name);
    pg_sw_anim(pg, *cur_pg, back_to_anim_completed_cb);
}

void hmi_back_home()
{
    if (lv_ll_get_len(&pg_stack) <= 1)
    {
        LV_LOG_WARN("It's already the bottom of the stack!");
        return;
    }

    hmi_pg_t **cur_pg = lv_ll_get_tail(&pg_stack);
    hmi_pg_t **home_pg = lv_ll_get_head(&pg_stack);

    LV_LOG_USER("[%s]-->[%s]", (*cur_pg)->name, (*home_pg)->name);
    pg_sw_anim(*home_pg, *cur_pg, back_to_anim_completed_cb);
}

void hmi_rebuild_all_pg()
{
    hmi_pg_t **pg = lv_ll_get_head(hmi_get_pg_list());
    while (pg)
    {
        if ((*pg)->create && (*pg)->del)
        {
            LV_LOG_USER("Deleting the page[%s]...", (*pg)->name);
            (*pg)->del();
            (*pg)->create();
        }
        pg = lv_ll_get_next(hmi_get_pg_list(), pg);
    }

    pg = lv_ll_get_tail(&pg_stack);
    lv_screen_load((*pg)->root);
}

hmi_pg_t *hmi_get_cur_page()
{
    hmi_pg_t **pg = lv_ll_get_tail(&pg_stack);
    return *pg;
}

/**********************
 *   STATIC FUNCTIONS
 **********************/
static void dect_zone_ev_cb(lv_event_t *e)
{
    static lv_point_t start_point;

    lv_obj_t *obj = lv_event_get_target(e);
    lv_event_code_t code = lv_event_get_code(e);

    switch (code)
    {
    case LV_EVENT_PRESSED:
        lv_indev_get_point(lv_indev_active(), &start_point);
        break;

    case LV_EVENT_RELEASED:
        lv_point_t point;
        lv_indev_get_point(lv_indev_active(), &point);

        if ((obj == left_dect_zone || obj == right_dect_zone) && (LV_ABS(point.x - start_point.x) > HMI_NAV_GESTURE_THRESH))
            hmi_pg_pop();

        if ((obj == bottom_dect_zone) && (LV_ABS(point.y - start_point.y) > HMI_NAV_GESTURE_THRESH))
            hmi_back_home();

        break;

    default:
        break;
    }
}

static hmi_pg_t **locate_pg_in_stack(hmi_pg_t *pg)
{
    hmi_pg_t **pg_node = lv_ll_get_head(&pg_stack);

    if (!pg)
    {
        LV_LOG_WARN("The bottom of the stack is empty.");
        return NULL;
    }

    while (pg_node)
    {
        if (*pg_node == pg)
            return pg_node;

        pg_node = lv_ll_get_next(&pg_stack, pg_node);
    }

    return NULL;
}

static void pg_exit_anim_completed_cb(lv_anim_t *a)
{
    hmi_pg_t *enter_pg = lv_anim_get_user_data(a);
    lv_scr_load(enter_pg->root);
}

static void push_anim_completed_cb(lv_anim_t *a)
{
    LV_UNUSED(a);

    lv_indev_enable(NULL, true);
}

static void pop_anim_completed_cb(lv_anim_t *a)
{
    LV_UNUSED(a);

    lv_indev_enable(NULL, true);

    hmi_pg_t **exit_pg = lv_ll_get_tail(&pg_stack);

    if (!(--(*exit_pg)->statck_refs) && (*exit_pg)->del_on_pop && (*exit_pg)->del)
    {
        LV_LOG_USER("Deleting the page[%s]...", (*exit_pg)->name);
        (*exit_pg)->del();
        hmi_log_out_pg((*exit_pg));
    }

    lv_ll_remove(&pg_stack, exit_pg);
    lv_free(exit_pg);
}

static void back_to_anim_completed_cb(lv_anim_t *a)
{
    LV_UNUSED(a);

    lv_indev_enable(NULL, true);

    hmi_pg_t **pg_node = lv_ll_get_tail(&pg_stack);

    while ((*pg_node)->root != lv_screen_active())
    {
        if (!(--(*pg_node)->statck_refs) && (*pg_node)->del_on_pop && (*pg_node)->del)
        {
            LV_LOG_USER("Deleting the page[%s]...", (*pg_node)->name);
            (*pg_node)->del();
            hmi_log_out_pg(*pg_node);
        }

        lv_ll_remove(&pg_stack, pg_node);
        hmi_pg_t **tmp = pg_node;
        pg_node = lv_ll_get_prev(&pg_stack, pg_node);
        lv_free(tmp);
    }
}

static void pg_sw_anim(hmi_pg_t *enter_pg, hmi_pg_t *exit_pg, lv_anim_completed_cb_t enter_cb)
{
    lv_indev_enable(NULL, false);

    if (exit_pg)
    {
        int32_t time = exit_pg->exit_anim(pg_exit_anim_completed_cb, enter_pg);
        enter_pg->enter_anim(enter_cb, time);
    }
    else
        enter_pg->enter_anim(enter_cb, 0);
}
